package com.theblind.agent;

import com.theblind.annotation.Oversee;
import com.theblind.utils.StringUtil;
import javassist.*;
import javassist.bytecode.MethodInfo;
import javassist.expr.ExprEditor;
import javassist.expr.MethodCall;

import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.lang.instrument.ClassFileTransformer;
import java.security.ProtectionDomain;
import java.time.Duration;

/**
 * @program: SyncAgent
 * @description:
 * @author: TheBlind
 * @create: 2020-12-19 14:37
 **/
public class MonitorTransformer implements ClassFileTransformer {

    /**
     * 是否全部转换完成
     */
    public static boolean allTransformerStatus = true;

    public static final String RETURN_FIELD_ID = " m1";
    public static final String RETURN_FIELD_DESCRIBE = " m2";

    private static final String INITIALIZE_VALUE_START = " String m1=null;String m2=null;";

    private static final String PACKAGE_NAME = MonitorTransformer.class.getPackage().getName() + ".";
    private static final String INITIALIZE_VALUE_END = PACKAGE_NAME + "MonitoringCache.updateTopOfStack(m1,m2);";
    /**
     * 步骤中 返回的标识
     */
    private static final String RETURN_KEY = "@Return";
    /**
     * 代理类
     */
    private static final String PROXY_CLASS = "$";

    /**
     * 自定义 方法代理的后缀
     */
    private static final String CUSTOMIZE_AGENT_METHOD_SUFFIX = "$agentMethod";

    /**
     * 直接指定 需要访问的包 通过设置 -javaagent:xx.jar=com.dss.synchronization
     */
    public static String visitPage = "";


    @Override
    public byte[] transform(ClassLoader loader, String className, Class<?> classBeingRedefined, ProtectionDomain protectionDomain, byte[] classfileBuffer) {
        Class<?> clazz = null;
        try {

            if (className == null) {
                return null;
            }
            String currentClassName = className.replaceAll("/", ".");
            if (!currentClassName.contains(visitPage) || currentClassName.contains(PROXY_CLASS)) {
                return null;
            }
            ClassPool aDefault = ClassPool.getDefault();
            CtClass ctClass = aDefault.get(currentClassName);
            CtMethod[] methods1 = ctClass.getMethods();
            for (CtMethod ctMethod : methods1) {
                Oversee annotation = getAnnotation(ctMethod);
                if (annotation != null) {
                    System.out.println("转化--" + currentClassName);
                    StringBuilder buffer = new StringBuilder("{");
                    buffer.append(INITIALIZE_VALUE_START);
                    if ("".equals(annotation.value()) || annotation.value() == null) {
                        buffer.append(stepVerifyAndReplacement(annotation.getValueSteps(), RETURN_FIELD_ID));
                    }
                    if ("".equals(annotation.describe()) || annotation.describe() == null) {
                        buffer.append(stepVerifyAndReplacement(annotation.getDescribeSteps(), RETURN_FIELD_DESCRIBE));
                    }
                    //更新栈顶
                    buffer.append(INITIALIZE_VALUE_END);
                    buffer.append("}");
                    MethodInfo methodInfo = ctMethod.getMethodInfo();
                    int ctMethodMaxLine = methodInfo.getLineNumber(Integer.MAX_VALUE);
                    //插入到最后一行
                    ctMethod.insertAt(ctMethodMaxLine-1,buffer.toString());
                    aop(ctClass, ctMethod, annotation);
                }
            }
            return ctClass.toBytecode();
        } catch (Exception e) {
            e.printStackTrace();
        }
        return null;
    }
    private static void outputClazz(byte[] bytes,String name) {
        // 输出类字节码
        FileOutputStream out = null;
        try {
            String pathName = MonitorTransformer.class.getResource("/").getPath() +name+ ".class";
            out = new FileOutputStream(new File(pathName));
            System.out.println("类输出路径：" + pathName);
            out.write(bytes);
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            if (null != out) try {
                out.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }

    /**
     *
     * 代理之前
     * @param oversee
     * @return
     */
    private static String before(Oversee oversee) {
        StringBuilder buffer = new StringBuilder();
        buffer.append(PACKAGE_NAME + "MonitoringCache.MonitoringData monitoringData = new " + PACKAGE_NAME + "MonitoringCache.MonitoringData();");
        //是否是组
        buffer.append("   monitoringData.setGroup(").append(oversee.isGroup()).append(");");
        //所属组
        buffer.append("   monitoringData.setBelongGroup(\"").append(oversee.belongGroup()).append("\");");
        //设置Id
        buffer.append("  monitoringData.setId(\"").append(oversee.value()).append("\");");
        //描述
        buffer.append("  monitoringData.setDescribe(\"").append(oversee.describe()).append("\");");
        //入栈
        buffer.append(PACKAGE_NAME + "MonitoringCache.addStack(monitoringData);");
        return buffer.toString();
    }

    private static void aop(CtClass ctClass, CtMethod ctMethod, Oversee oversee) throws CannotCompileException, NotFoundException {
        String name = ctMethod.getName();
        String newName = name + CUSTOMIZE_AGENT_METHOD_SUFFIX;
        ctMethod.setName(newName);
        CtMethod newMethod = CtNewMethod.copy(ctMethod, name, ctClass, null);
        String returnType = newMethod.getReturnType().getName();
        StringBuilder body = new StringBuilder("{\n");
        body.append(before(oversee));
        if (!"void".equals(returnType)) {
            body.append(returnType + " result = ");
        }
        body.append(newName + "($$);\n");
        body.append(after());
        if (!"void".equals(returnType)) {
            body.append("return result; \n");
        }
        body.append("\n}");
        newMethod.setBody(body.toString());
        ctClass.addMethod(newMethod);
    }

    /**
     * 代理之后
     * @return
     */
    private static String after() {
        StringBuilder buffer = new StringBuilder();
        //出栈
        buffer.append(PACKAGE_NAME + "MonitoringCache.popStack();");
        return buffer.toString();
    }

    /**
     * 验证和替换 步骤
     *
     * @return
     */
    private static String stepVerifyAndReplacement(String v, String replaceValue) throws Exception {
        if (StringUtil.isEmpty(v)) {
            allTransformerStatus = false;
            throw new NullPointerException("Steps Null");
        }
        if (!v.contains(RETURN_KEY)) {
            allTransformerStatus = false;
            throw new RuntimeException("@Return Not Found");
        }
        return v.replace(RETURN_KEY, replaceValue);
    }

    private Oversee getAnnotation(CtMethod method) throws ClassNotFoundException {
        Oversee annotation = (Oversee) method.getAnnotation(Oversee.class);
        return annotation;
    }


}